open Type
open List
open Lexer


(*
error : string -> unit
print line number and error message
*)
let error = function str -> print_string 
            ("[line "^string_of_int (!lineno-1)^"] "^str) ; 
            print_newline () ; exit 1
;;


(* return value of var or NotFound *)
let rec get_var_int name vlist =
	match vlist with
	  [] -> raise NotFound
	| (var2, value)::tail -> if (compare var2 name) == 0 then value 
                           else get_var_int name tail
;;

let rec get_var name env = 
	match name with
	  Array_index(var,index) ->
		(try let value = get_var_int var env in
			begin match value with
			  Array(tab) -> begin match index with
							  Var(var2) -> let val2 = get_var (Var(var2)) env in
									begin match val2 with
									  Int(value) -> (try List.nth tab value with
											  Failure "nth" -> error ("index `"^string_of_int value^
                                                "`out of bounds in "^var)
											| Invalid_argument "List.nth" -> 
                                          error ("negative index in"^var))
											| _ -> error "array access expects integer value"
									end
							| Int(value) -> (try List.nth tab value with
									  Failure "nth" -> error ("index `"^string_of_int value^
                                             "`out of bounds in "^var)
									| Invalid_argument "List.nth" -> error 
                                                   ("negative index in"^var))
							| x -> error "This is a limitation"
								(* this is really easy to add but we need to use 
                parse_int_expr in main.mll ...
								(try let val3 = parse_int_expr x env in 
									(try List.nth tab val3 with
										Failure "nth" -> error ("index out of bounds in "^var)
									| Invalid_argument "List.nth" -> 
                                            error ("negative index in"^var))
								with TypeError -> error "array access expects integer value")
								*)
							end
			| _ -> error ("variable isn't an array")
			end
		with NotFound -> error ("unknown variable :"^var))
	| Var(var) -> (try get_var_int var env
					with NotFound -> error ("unknown variable :"^var))
	| Function(name, args_list) -> error "This is a limitation"
								(* same thing as above *)
	| _ -> raise TypeError
;;

let rec remove_first var env =
	match env with
	  [] -> []
	| (var2, value)::tail -> if (compare var var2) == 0 then tail 
                           else (var2, value)::(remove_first var tail)
;;

(* modify an element in array *)
let modif_element arr index new_value = 

	let rec modif_element_int arr index new_value current_index =
		match arr with
		  [] -> []
		| head::tail -> if index == current_index then new_value::tail
							else head::(modif_element_int tail index new_value 
                                              (current_index+1))
								
	in
	modif_element_int arr index new_value 0
;;

let rec add_var var value env =
	match var with
	  Var(var2) -> 
				if (compare (String.uppercase var2) var2 == 0 ) (* if it's a
                                                          constant *)
				then begin
					(* if already present in env, we can't modify it *)
					(try let _ = get_var_int var2 env in error ("The variable `"^var2^
                                    "` is a constant and can't be modified")
						with NotFound -> let new_env = remove_first var2 env in
                                           (var2, value)::new_env)
					
				end else
					let new_env = remove_first var2 env in (var2, value)::new_env
	| Array_index(var2, index) ->
		let arr = get_var (Var(var2)) env and new_env = remove_first var2 env in
			begin match arr, index with
			  Array(arr2), Var(var3) ->
								let val3 = get_var (Var(var3)) env in
									begin match val3 with
									  Int(x) -> let new_array = modif_element arr2 x value in
                                             (var2, Array(new_array))::new_env
									| _ -> error "array access expects integer value"
									end
			| Array(arr2), Int(x) -> let new_array = modif_element arr2 x value in
									(var2, Array(new_array))::new_env
			| Array(arr2), _ -> error "array access expects integer value"

					
			| _ , _-> error ("variable '"^var2^"' isn't an array")
			end
	| _ -> error "Bad identifier, can't be used for a variable"
;;

let rec add_var_function name list_var list_value env =
	match list_var, list_value with
	  [], [] -> env
	| [], x -> error ("function '"^name^"'is applied to too many arguments")
	| Var(name2)::tail, [] ->	error 
                            ("function '"^name^"' is applied to few arguments")
	| Var(name2)::tail, head2::tail2 ->
		begin match head2 with
		  Int(_) -> (add_var_function name tail tail2 ((name2, head2)::env))
		| Float(_)-> (add_var_function name tail tail2 ((name2, head2)::env))
		| String(_)-> (add_var_function name tail tail2 ((name2, head2)::env))
		| Bool(_) -> (add_var_function name tail tail2 ((name2, head2)::env))
		| Array(_) -> (add_var_function name tail tail2 ((name2, head2)::env))
		| Var(x) -> let value = get_var (Var(x)) env in (add_var_function name 
                            tail tail2 ((name2, value)::env))
		| Array_index(name, idx) -> let value = get_var (Array_index(name, idx)) 
                                env in (add_var_function name tail tail2 
                                ((name2, value)::env))
		| _ -> error "This is a limitation"
			(* if we're here, we have to calcul 
      expression received with eval function *)
		end 
	| _,_ -> error "huhu"
;;
